package com.cgrs572.shortlink.admin.common.biz.user;

import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.Optional;

/**
 * 用户上下文
 * 将用户上下文信息存储到ThreadLocal工具类中
 * 1.什么是ThredLocal工具类？
 * ThredLocal工具类用于创建线程的本地变量，每个线程在访问该变量时都有自己独立的副本，所以变量之间互不干扰。他又两个主要功能：
 * （1）实现资源对象的线程隔离，让每个线程各用各的资源对象，避免因征用引发的线程安全问题
 * （2）实现线程内的资源共享
 * 2.ThredLocal工具类原理？
 * （1）每个线程对象（即Thread）都有自己的ThreadLocal对象，ThrealLocal工具类内部有一个ThreadLocalMap静态类，用来存储资源对象；
 * （2）当调用set方法的时候，ThreadLocal会已自己作为键，将资源对象存入到当前线程的ThreadLocalMap中；
 * （3）当调用get方法的时候，ThreadLocl会以自己作为键，从当前线程的ThreadLocalMap中获取资源对象；
 * （4）当调用remove方法发时，Thread会以自己作为键，删除当前线程的ThreadLocalMap中的资源对象
 * 3.ThreadLocal是否会发生内存溢出，为什么？
 * （1）ThreadLocalMap中的键ThreadLocal是弱引用，如果没有强引用指向它，则会在下一次垃圾回收时被GC回收；
 * （2）而ThreadLocalMap中的Value值是强引用，只要ThreadLocal存在就不会被GC回收；
 *     因此在线程池中，线程的复用可能存在旧值未被清理的情况，导致内存泄露。
 * 4.内存溢出的解决方式
 * （1）每次使用完ThreadLocal后主动调用remove()方法清楚数据；
 * （2）利用static final将ThreadLocal设置为静态常量
 * (3)注意：以上两个方式必须联合使用才能解决问题，因为若单独使用(2)，ThreadLocalMap会持续增长，最后仍会造成OOM（即内存溢出）
 * 本代码为了防止内存泄露就将以上两种方式结合起来使用了
 */
public final class UserContext {
    // 使用阿里的TTL线程---它可以在当前线程及其子线程之间传递线程变量（此处为 UserInfoDTO 对象），属于线程安全的
    // 作用：定义一个线程局部变量 USER_THREAD_LOCAL，用于存储当前线程的用户信息（即UserInfoDTO），并且支持在线程池或子线程中自动传递这个信息
    //  <a href="https://github.com/alibaba/transmittable-thread-local" />
    private static final ThreadLocal<UserInfoDTO> USER_THREAD_LOCAL = new TransmittableThreadLocal<>();

    /**
     * 设置用户至上下文
     * @param user 用户详情信息
     */
    public static void setUser(UserInfoDTO user) {
        USER_THREAD_LOCAL.set(user);
    }

    /**
     * 获取上下文中用户 ID
     * @return 用户 ID
     */
    public static String getUserId() {
        UserInfoDTO userInfoDTO = USER_THREAD_LOCAL.get();
        return Optional.ofNullable(userInfoDTO).map(UserInfoDTO::getUserId).orElse(null);
    }

    /**
     * 获取上下文中用户名称
     * @return 用户名称
     */
    public static String getUsername() {
        UserInfoDTO userInfoDTO = USER_THREAD_LOCAL.get();
        return Optional.ofNullable(userInfoDTO).map(UserInfoDTO::getUsername).orElse(null);
    }

    /**
     * 获取上下文中用户真实姓名
     * @return 用户真实姓名
     */
    public static String getRealName() {
        UserInfoDTO userInfoDTO = USER_THREAD_LOCAL.get();
        return Optional.ofNullable(userInfoDTO).map(UserInfoDTO::getRealName).orElse(null);
    }

    /**
     * 将当前用户从上下文中清理掉，防止内存泄露
     * 注意：如果线程池中的线程长时间存活，且没有调用 remove() 方法清除线程本地变量，可能会导致对象无法被回收，进而导致内存泄露
     */
    public static void removeUser() {
        USER_THREAD_LOCAL.remove();
    }
}
